<?php
/**
 * Rate Limiting Middleware
 * Implements API rate limiting and anti-brute force protection
 */

namespace app\api\middleware;

use Webman\Http\Request;
use Webman\Http\Response;
use Webman\MiddlewareInterface;
use support\Log;
use think\facade\Cache;

class RateLimitMiddleware implements MiddlewareInterface
{
    /**
     * Rate limit configurations
     */
    private $rateLimits;
    
    public function __construct()
    {
        $config = config('security.rate_limit', []);
        
        $this->rateLimits = [
            'default' => [
                'limit' => $config['default']['limit'] ?? 100,
                'window' => $config['default']['window'] ?? 3600,
                'key_prefix' => 'rate_limit:'
            ],
            'auth' => [
                'limit' => $config['auth']['limit'] ?? 10,
                'window' => $config['auth']['window'] ?? 300,
                'key_prefix' => 'auth_rate_limit:'
            ],
            'api' => [
                'limit' => $config['api']['limit'] ?? 1000,
                'window' => $config['api']['window'] ?? 3600,
                'key_prefix' => 'api_rate_limit:'
            ],
            'sensitive' => [
                'limit' => $config['sensitive']['limit'] ?? 20,
                'window' => $config['sensitive']['window'] ?? 60,
                'key_prefix' => 'sensitive_rate_limit:'
            ]
        ];
    }

    /**
     * Process the request and apply rate limiting
     */
    public function process(Request $request, callable $handler): Response
    {
        $clientId = $this->getClientIdentifier($request);
        $rateLimitConfig = $this->getRateLimitConfig($request);
        
        if ($this->isRateLimited($clientId, $rateLimitConfig)) {
            return $this->rateLimitExceededResponse($rateLimitConfig);
        }
        
        // Process the request
        $response = $handler($request);
        
        // Add rate limit headers to response
        return $this->addRateLimitHeaders($response, $clientId, $rateLimitConfig);
    }
    
    /**
     * Get client identifier for rate limiting
     */
    private function getClientIdentifier(Request $request): string
    {
        // Try to get authenticated user ID first
        if ($request->user && isset($request->user->id)) {
            return 'user_' . $request->user->id;
        }
        
        // For auth endpoints, use IP + user agent to prevent brute force
        if ($this->isAuthEndpoint($request)) {
            return 'auth_' . md5($request->getRealIp() . $request->header('User-Agent', ''));
        }
        
        // Default to IP address
        return 'ip_' . $request->getRealIp();
    }
    
    /**
     * Get appropriate rate limit configuration
     */
    private function getRateLimitConfig(Request $request): array
    {
        if ($this->isAuthEndpoint($request)) {
            return $this->rateLimits['auth'];
        }
        
        if ($this->isSensitiveEndpoint($request)) {
            return $this->rateLimits['sensitive'];
        }
        
        if ($this->isApiEndpoint($request)) {
            return $this->rateLimits['api'];
        }
        
        return $this->rateLimits['default'];
    }
    
    /**
     * Check if request is to an authentication endpoint
     */
    private function isAuthEndpoint(Request $request): bool
    {
        $authPaths = ['/api/auth/login', '/api/auth/register', '/api/users/login', '/api/users/register', '/api/admin/login'];
        $path = $request->path();
        
        foreach ($authPaths as $authPath) {
            if (strpos($path, $authPath) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Check if request is to a sensitive endpoint
     */
    private function isSensitiveEndpoint(Request $request): bool
    {
        $sensitivePaths = ['/api/apps/create', '/api/apps/delete', '/api/apps/update', '/api/balance/create-recharge-order'];
        $path = $request->path();
        
        foreach ($sensitivePaths as $sensitivePath) {
            if (strpos($path, $sensitivePath) !== false) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Check if request is to an API endpoint
     */
    private function isApiEndpoint(Request $request): bool
    {
        return strpos($request->path(), '/api/') === 0;
    }
    
    /**
     * Check if client is rate limited
     */
    private function isRateLimited(string $clientId, array $config): bool
    {
        $key = $config['key_prefix'] . $clientId;
        $current = Cache::get($key, 0);
        
        return $current >= $config['limit'];
    }
    
    /**
     * Increment request counter
     */
    private function incrementRequestCounter(string $clientId, array $config): void
    {
        $key = $config['key_prefix'] . $clientId;
        $current = Cache::get($key, 0);
        
        if ($current === 0) {
            Cache::set($key, 1, $config['window']);
        } else {
            Cache::inc($key);
        }
    }
    
    /**
     * Get remaining requests for client
     */
    private function getRemainingRequests(string $clientId, array $config): int
    {
        $key = $config['key_prefix'] . $clientId;
        $current = Cache::get($key, 0);
        
        return max(0, $config['limit'] - $current);
    }
    
    /**
     * Get reset time for rate limit window
     */
    private function getResetTime(string $clientId, array $config): int
    {
        $key = $config['key_prefix'] . $clientId;
        $ttl = Cache::ttl($key);
        
        return time() + ($ttl > 0 ? $ttl : $config['window']);
    }
    
    /**
     * Generate rate limit exceeded response
     */
    private function rateLimitExceededResponse(array $config): Response
    {
        $retryAfter = $config['window'];
        
        return json([
            'code' => 429,
            'msg' => 'Too many requests. Please try again later.',
            'data' => [
                'retry_after' => $retryAfter,
                'limit' => $config['limit'],
                'window' => $config['window']
            ]
        ], 429)->withHeaders([
            'Retry-After' => $retryAfter,
            'X-RateLimit-Limit' => $config['limit'],
            'X-RateLimit-Window' => $config['window']
        ]);
    }
    
    /**
     * Add rate limit headers to response
     */
    private function addRateLimitHeaders(Response $response, string $clientId, array $config): Response
    {
        // Increment counter after successful request
        $this->incrementRequestCounter($clientId, $config);
        
        $remaining = $this->getRemainingRequests($clientId, $config);
        $resetTime = $this->getResetTime($clientId, $config);
        
        return $response->withHeaders([
            'X-RateLimit-Limit' => $config['limit'],
            'X-RateLimit-Remaining' => $remaining,
            'X-RateLimit-Reset' => $resetTime,
            'X-RateLimit-Window' => $config['window']
        ]);
    }
}